#include "http_server.h"
#include "io_config.h"
#include "api_map.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "esp_log.h"

#define TAG "api.sleep"
#define log_e(...) ESP_LOGE(TAG, __VA_ARGS__)
#define log_i(...) ESP_LOGI(TAG, __VA_ARGS__)
#define log_w(...) ESP_LOGW(TAG, __VA_ARGS__)

#if API_ENABLE_CONFIG_SLEEP

#define WAKEUP_IO_DEFAULT_LEVEL 0

#ifdef CONFIG_IDF_TARGET_ESP32
#define WAKEUP_IO_MAX 18
#elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3)
#define WAKEUP_IO_MAX 6
#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
#define WAKEUP_IO_MAX 22
#else
#define WAKEUP_IO_MAX 10
#endif

#include "config.h"
#pragma pack(1)
typedef struct wakeup_config
{
    uint32_t timer;
    bool enable;
    uint8_t num;
    int8_t io[WAKEUP_IO_MAX]; /* -1-254 */
} wakeup_config_s, *wakeup_config_t;
#pragma pack()
wakeup_config_t wakeup_config = NULL;
static void wakeup_config_init(void)
{
    wakeup_config = malloc(sizeof(wakeup_config_s));
    log_i("malloc wakeup_config(%d) : %p", sizeof(wakeup_config_s), wakeup_config);
    if (!wakeup_config)
        return;
    memset(wakeup_config, 0, sizeof(wakeup_config_s));
    if (!config_get("WakeupConfig", (uint8_t *)wakeup_config, sizeof(wakeup_config_s)))
    {
        log_w("Not find WakeupConfig, Use default config");
        /* 默认配置 */
        wakeup_config->enable = true;
        wakeup_config->timer = 0;
        wakeup_config->num = 1;
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3)
        wakeup_config->io[0] = 9;
#else
        wakeup_config->io[0] = 0;
#endif
    }
    log_i("wakeup_config: enable=%d, timer=%ld", wakeup_config->enable, wakeup_config->timer);
    for (uint8_t i = 0; i < wakeup_config->num; i++)
        log_i("wakeup_io%d:%d", i, wakeup_config->io[i]);
}
static bool wakeup_config_save(void)
{
    if (wakeup_config && config_set("WakeupConfig", sizeof(wakeup_config_s), (uint8_t *)wakeup_config))
        return config_commit();
    return false;
}
static uint16_t wakeup(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (!wakeup_config)
        return 0;
    if (data) /* set */
    {
        /*
        {
            "wakeup":[
                {
                    "enable":true,
                    "timer":30000,
                    "gpio":[0]
                }
            ],
            ...
        }
         */
        if (cJSON_HasObjectItem(data, "wakeup"))
        {
            log_i("process wakeup config");
            cJSON *wakeup = cJSON_GetObjectItem(data, "wakeup");
            if (cJSON_IsArray(wakeup))
            {
                cJSON *item = cJSON_GetArrayItem(wakeup, 0);
                if (item)
                {
                    if (cJSON_IsObject(item))
                    {
                        uint8_t change = 0;
                        cJSON *enable = cJSON_GetObjectItem(item, "enable");
                        cJSON *timer = cJSON_GetObjectItem(item, "timer");
                        cJSON *gpio = cJSON_GetObjectItem(item, "gpio");
                        if (cJSON_IsBool(enable))
                        {
                            if (wakeup_config->enable != cJSON_IsTrue(enable))
                            {
                                change = 1;
                                wakeup_config->enable = cJSON_IsTrue(enable);
                            }
                        }
                        if (cJSON_IsNumber(timer))
                        {
                            if (wakeup_config->timer != (uint32_t)timer->valueint)
                            {
                                change = 1;
                                wakeup_config->timer = (uint32_t)timer->valueint;
                            }
                        }
                        if (cJSON_IsArray(gpio))
                        {
                            uint8_t num = cJSON_GetArraySize(gpio);
                            if (wakeup_config->num != num)
                            {
                                if (num > WAKEUP_IO_MAX)
                                {
                                    char *str = (char *)server->data;
                                    uint16_t source_len = strlen(str);
                                    uint16_t len = source_len;
                                    len += sprintf(&str[len], "\"wakeup\":[{\"code\":%d, \"msg\":\"GPIO列表个数超过%d个\"}],", -1, WAKEUP_IO_MAX);
                                    return len - source_len;
                                }
                                change = 1;
                                wakeup_config->num = num;
                            }
                            for (int i = 0; i < wakeup_config->num; i++)
                            {
                                cJSON *item = cJSON_GetArrayItem(gpio, i);
                                if (wakeup_config->io[i] != (uint8_t)item->valueint)
                                {
                                    change = 1;
                                    wakeup_config->io[i] = (uint8_t)item->valueint;
                                }
                            }
                        }
                        if (change)
                        {
                            bool res = wakeup_config_save();
                            if (!res)
                                log_e("wakeup_config_save failed");
                            char *str = (char *)server->data;
                            uint16_t source_len = strlen(str);
                            uint16_t len = source_len;
                            if (res)
                                len += sprintf(&str[len], "\"wakeup\":[{\"code\":%d}],", 0);
                            else
                                len += sprintf(&str[len], "\"wakeup\":[{\"code\":%d, \"msg\":\"保存失败\"}],", -1);
                            return len - source_len;
                        }
                    }
                }
            }
        }
        return 0;
    }
    else /* get */
    {
        char *str = (char *)server->data;
        uint16_t source_len = strlen(str);
        uint16_t len = source_len;
        len += sprintf(&str[len], "\"wakeup\":[{");
        len += sprintf(&str[len], "\"enable\":%s,", wakeup_config->enable ? "true" : "false");
        len += sprintf(&str[len], "\"timer\":%ld,", wakeup_config->timer); /* 定时唤醒 */
        len += sprintf(&str[len], "\"gpio\":[");
        for (int i = 0; i < wakeup_config->num; i++)
            len += sprintf(&str[len], "%d,", wakeup_config->io[i]);
        if (wakeup_config->num > 0)
            len -= 1;
        len += sprintf(&str[len], "]}],");
        return len - source_len;
    }
}

static void call_sleep(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
    uint8_t have_wakeup_source = 0;
    uint32_t wakeup_time_ms = 0;

#if API_ENABLE_CONFIG_SLEEP
    if (wakeup_config)
    {
        wakeup_time_ms = wakeup_config->timer;
#if SOC_PM_SUPPORT_EXT_WAKEUP
        uint8_t wakeup_io_num = wakeup_config->num;
#if !defined(SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN)
        uint64_t wakeup_mask = 0;
#endif
        for (int i = 0; i < wakeup_io_num; i++)
        {
            if ((int8_t)wakeup_config->io[i] == -1)
                continue;
            if (!rtc_gpio_is_valid_gpio(wakeup_config->io[i]))
            {
                log_e("GPIO %d is not an RTC IO", wakeup_config->io[i]);
                continue;
            }
            log_i("WAKEUP IO%d", wakeup_config->io[i]);
#ifdef CONFIG_SOC_GPIO_SUPPORT_DEEPSLEEP_WAKEUP
            const gpio_config_t config = {
                .pin_bit_mask = BIT(wakeup_config->io[i]),
                .mode = GPIO_MODE_INPUT,
                .pull_up_en = !WAKEUP_IO_DEFAULT_LEVEL,
                .pull_down_en = WAKEUP_IO_DEFAULT_LEVEL,
                .intr_type = GPIO_INTR_POSEDGE,
            };
            gpio_config(&config);
            esp_deep_sleep_enable_gpio_wakeup(BIT(wakeup_config->io[i]), WAKEUP_IO_DEFAULT_LEVEL);
#else
#if WAKEUP_IO_DEFAULT_LEVEL
            rtc_gpio_pullup_dis(wakeup_config->io[i]);
            rtc_gpio_pulldown_en(wakeup_config->io[i]);
#else
            rtc_gpio_pulldown_dis(wakeup_config->io[i]);
            rtc_gpio_pullup_en(wakeup_config->io[i]);
#endif
#if defined(CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP)
#ifdef SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN
            esp_sleep_enable_ext1_wakeup(1ULL << wakeup_config->io[i], WAKEUP_IO_DEFAULT_LEVEL);
#else
            wakeup_mask |= (1ULL << wakeup_config->io[i]);
#endif
#else
            log_w("No deep sleep gpio wakeup source");
#endif
#endif
            have_wakeup_source = 1;
        }
#if !defined(SOC_PM_SUPPORT_EXT1_WAKEUP_MODE_PER_PIN)
        if (wakeup_mask)
            esp_sleep_enable_ext1_wakeup(wakeup_mask, WAKEUP_IO_DEFAULT_LEVEL);
#endif
#endif // SOC_PM_SUPPORT_EXT_WAKEUP
    }
#endif /* API_ENABLE_CONFIG_SLEEP */

#if CONFIG_IDF_TARGET_ESP32
    rtc_gpio_isolate(GPIO_NUM_12);
#endif // CONFIG_IDF_TARGET_ESP32

    if (!have_wakeup_source || wakeup_time_ms)
    {
        // uint64_t timeout = -1; /* forever */
        uint64_t timeout = 30 * 1000000ll; /* 30s test */
        if (wakeup_time_ms)
            timeout = wakeup_time_ms * 100000ll;
        wakeup_time_ms = timeout / 1000;
        uint16_t ms = wakeup_time_ms % 1000;
        uint8_t s = (wakeup_time_ms / 1000) % 60;
        uint8_t m = (wakeup_time_ms / 1000 / 60) % 60;
        uint8_t h = (wakeup_time_ms / 1000 / 60 / 60) % 24;
        log_i("wakeup timer source, time=%02d:%02d:%02d.%03d", h, m, s, ms);
        ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(timeout));
    }

    log_i("Start Deep sleep");
    // if (have_wakeup_source)
    //     esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO);
    esp_deep_sleep_start();
}

static void sleep_init(void)
{
    wakeup_config_init();
#if API_ENABLE_CONFIG_IO
    io_config_add(wakeup);
#endif
    ADD_API_ITEM_ALIAS("deep_sleep", call_sleep);
}
ADD_API(sleep_init)
#endif
